Here are some sample plots using the voting data. Any thoughts about what this tells us for analysis?
Setup
Note the setup variables below. These are supposed to be controls all the plots. Sometimes they are. Sometimes not. Have to clean this up, but the goal is uniformity across all the plots.
For fonts, I tried to use ‘sans’ which should pick the system sans serif font for whatever OS is running. In the plotly plot I had to pick a specific font or it defaults to Times New Roman. I picked Arial, but we should look at including Helvetica for macOS users.
# --- Load libraries ---library(ggplot2)library(ggforce)library(dplyr)library(knitr)library(readr)library(sf)library(tigris)library(plotly)# --- Global color palette (Civic Triangle style) ---fill_col <-"#ffffff"# white backgroundline_col <-"#3a5f7d"# roads / accentstext_col <-"#2f3b44"# textborder_col <-"#e6eef5"# county borders (light blue-grey)# --- Load Texas county-level data ---turnout <-read_csv("tx_county_voting_data_2020.csv")# --- Compute voter turnout rate ---# Adds a new variable Turnout_Rate as a percentageturnout <- turnout %>%mutate(Turnout_Rate = (Total_Votes_Cast / Registered_Voters) *100 )# --- Preview the first few rows to confirm ---head(turnout)
library(ggplot2)library(ggforce)# --- Triangle node coordinates ---triangle <-data.frame(label =c("Education", "Health", "Voter Turnout"),x =c(0, 1, 0.5),y =c(0, 0, sqrt(3)/2))# --- Arrows for feedback loops ---arrows <-data.frame(x =c(0.5, 1, 0),y =c(sqrt(3)/2, 0, 0),xend =c(1, 0, 0.5),yend =c(0, 0, sqrt(3)/2))# --- Blue-grey palette ---fill_col <-"#e6eef5"line_col <-"#3a5f7d"text_col <-"#2f3b44"# --- Plot ---p <-ggplot() +geom_polygon(data = triangle, aes(x, y),fill = fill_col, color = line_col, linewidth =1.2) +geom_point(data = triangle, aes(x, y),size =6, color = line_col) +geom_text(data = triangle, aes(x, y, label = label),vjust =c(1.5, 1.5, -2.2), # pull top label down slightlysize =5, family ="sans",fontface ="bold", color = text_col) +geom_curve(data = arrows, aes(x = x, y = y, xend = xend, yend = yend),curvature =-0.25,arrow =arrow(length =unit(0.3, "cm")),color = line_col, linewidth =0.8) +annotate("text", x =0.5, y =0.4,label ="Mutual Reinforcement\nand Feedback",color = text_col, size =4.2, family ="sans", lineheight =1.2) +theme_void() +coord_equal(xlim =c(-0.2, 1.2), ylim =c(-0.2, 1.05), clip ="off") +theme(plot.margin =margin(50, 50, 50, 50), # extra space all aroundplot.title =element_text(family ="sans", face ="bold", size =16,hjust =0.5, color = text_col ) ) +ggtitle("The Civic Triangle: Education, Health, and Voter Turnout")# --- Display inline ---p# --- Export PNG with full bounding box ---ggsave("civic_triangle_final.png", plot = p, width =9, height =8,dpi =300, units ="in", limitsize =FALSE)
Figure 1: The Civic Triangle linking Education, Health, and Voter Turnout as mutually reinforcing dimensions of civic vitality.
Figure 2
This chart shows name and turn out on hover.
The interesting thing to note is that the exurbs have higher turnout than urban cores, but the most rural areas have the lowest turnout. Might need to do some math on this based on classifying counties as rural, urban, and suburban to see what shakes out.
# --- Civic Triangle palette (non-data elements) ---fill_col <-"#ffffff"# white backgroundline_col <-"#3a5f7d"# blue-grey for roads and outlinestext_col <-"#2f3b44"# text and titlesborder_col <-"#e6eef5"# light blue-grey for county borders# --- Load Texas county geometries ---options(tigris_use_cache =TRUE)tx_counties <-counties(state ="TX", cb =TRUE, class ="sf") %>%mutate(County =gsub(" County", "", NAME))# --- Merge turnout and compute quintiles ---tx_map <- tx_counties %>%left_join(turnout, by ="County") %>%mutate(quintile =ntile(-Turnout_Rate, 5))# --- Load and simplify major roads ---roads <-primary_roads(class ="sf")roads_tx <-st_intersection(roads, st_union(st_geometry(tx_counties))) %>%st_cast("LINESTRING") %>%st_coordinates() %>%as.data.frame() %>%rename(lon = X, lat = Y)# --- Quintile fill palette (yellow → red) ---palette_turnout <-rev(c("#8b0000", "#d73a1f", "#f97c18", "#ffb94e", "#fff5a1"))# --- Build ggplot ---p_map <-ggplot() +geom_sf(data = tx_map,aes(fill =factor(quintile),text =paste0("<b>", County, " County</b><br>","Turnout Rate: ", round(Turnout_Rate, 1), "%" ) ),color = border_col, linewidth =0.25 ) +geom_path(data = roads_tx,aes(x = lon, y = lat, group = L1),color =adjustcolor(line_col, alpha.f =0.4),linewidth =0.4 ) +scale_fill_manual(values = palette_turnout,breaks =c("1", "2", "3", "4", "5"),labels =c("Highest Turnout", "", "", "", "Lowest Turnout"),name =NULL,drop =FALSE ) +coord_sf() +theme_void() +theme(plot.background =element_rect(fill = fill_col, color =NA),panel.background =element_rect(fill = fill_col, color =NA),legend.background =element_rect(fill = fill_col, color =NA),legend.position ="right",legend.direction ="vertical",legend.justification ="center",legend.key.width =unit(0.5, "cm"),legend.key.height =unit(0.8, "cm"),legend.text =element_text(family ="Arial", color = text_col, size =10),plot.title =element_text(family ="Arial", face ="bold", size =16,hjust =0.5, color = text_col ),plot.margin =margin(40, 40, 40, 40) ) +ggtitle("Texas County Voter Turnout, 2020")# --- Convert to interactive Plotly map ---p_map_interactive <-ggplotly(p_map, tooltip ="text") %>%layout(font =list(family ="Arial, Helvetica, sans-serif", color = text_col),paper_bgcolor = fill_col,plot_bgcolor = fill_col,title =list(text ="<b>Texas County Voter Turnout, 2020</b>",font =list(family ="Arial, Helvetica, sans-serif", size =18, color = text_col),x =0.5, xanchor ="center" ),legend =list(orientation ="v",x =1.02,y =0.5,xanchor ="left",yanchor ="middle",traceorder ="normal",font =list(family ="Arial, Helvetica, sans-serif", color = text_col, size =11) ) )# --- Fix legend labels in Plotly output (keep all colors visible) ---for (i inseq_along(p_map_interactive$x$data)) {if (!is.null(p_map_interactive$x$data[[i]]$name)) { p_map_interactive$x$data[[i]]$name <-switch( p_map_interactive$x$data[[i]]$name,"1"="Highest Turnout","5"="Lowest Turnout","2"=" ","3"=" ","4"=" ", p_map_interactive$x$data[[i]]$name ) }}p_map_interactive